package com.ht.dsp;

import _61970.core.Equipment;
import _61970.core.IdentifiedObject;
import _61970.generation.production.GeneratingUnit;
import _61970.meas.MeasurementType;
import _61970.topology.TopologicalNode;
import _61970.wires.ShuntCompensator;
import _61970.wires.SynchronousMachine;
import com.ht.aems.fesmodel.RtMeasureSaver;
import com.ht.aems.fesmodel.data.YCData;
import com.ht.aems.fesmodel.data.YXData;
import com.ht.aems.measure.MeasureInfo;
import com.ht.aems.ntp.*;
import com.ht.aems.se.SeResultInfo;
import com.ht.aems.util.RtDataHandler;
import com.ht.aems.util.SerializableUtil;
import org.apache.log4j.Logger;
import rtdb.IdCreator;
import rtdb.cim.CimKeys;
import rtdb.measure.MeasureKeys;
import rtdb.pas.SeKeys;

import java.util.*;

/**
 * This class has static functions:
 * <br>1.save cim index</br>
 * <br>2 save scada data</br>
 * <br>3 save expression data</br>
 * <br>4 save se result</br>
 *
 * @author Dong Shufeng
 *         Date: 11-8-17
 */
public class RtdbRefresher implements CimKeys, MeasureKeys, SeKeys, CIMConstants {
    private static Logger log = Logger.getLogger(RtdbRefresher.class);
    private static boolean isUpdatingCimIndex = false;
    private static String lastCimFile = "";

    public static void saveRawDataToRtdb(String instance) {
        RtDataHandler rtDataHandler = DspSysModel.INSTANCE.getRtDataHandler();
        long start = System.currentTimeMillis();
        try {
            long rawDataTime = DspSysModel.INSTANCE.getScadaDataTime();
            Map<String, Double> analogMap = DspSysModel.INSTANCE.getScadaAnalog();
            for (String point : analogMap.keySet())
                if (analogMap.get(point) != null)
                    rtDataHandler.set(IdCreator.createAnalogId(point, instance), analogMap.get(point));
            Map<String, Integer> discreteMap = DspSysModel.INSTANCE.getScadaDiscrete();
            for (String point : discreteMap.keySet())
                if (discreteMap.get(point) != null)
                    rtDataHandler.set(IdCreator.createDiscreteId(point, instance), discreteMap.get(point));
            rtDataHandler.set(KEY_SCADA_TIMESTAMP, rawDataTime);//update time
            updatePlantGenPower();//update substation's generator mw and mvar
            log.info("SCADA data has been updated to memory cache.");
        } catch (Exception e) {
            e.printStackTrace();
            log.warn("Exception occur when updating scada data to rtdb:" + e);
        }
        log.info("Time used for updating scada data to rtdb:" + (System.currentTimeMillis() - start) + "ms");
    }

    public static void saveSeDataToRtdb(YCData yc, YXData yx) {
        RtDataHandler rtDataHandler = DspSysModel.INSTANCE.getRtDataHandler();
        RtMeasureSaver rtMeasureSaver = DspSysModel.INSTANCE.getRtMeasureSaver();
        Map<CimToIEEE, SeResultInfo> seResults = DspSysModel.INSTANCE.getSeResults();
        long start = System.currentTimeMillis();
        try {
            rtDataHandler.set(KEY_SE_START_TIME, DspSysModel.INSTANCE.getSeStartTime());
            rtDataHandler.set(KEY_SE_END_TIME, DspSysModel.INSTANCE.getSeEndTime());
            rtDataHandler.set(KEY_SE_SECTION_TIME, DspSysModel.INSTANCE.getSeSectionTime());
            for (String point : DspSysModel.INSTANCE.getSeAnalog().keySet())
                rtDataHandler.set(point + SUFFIEX_SE_ANALOG, DspSysModel.INSTANCE.getSeAnalog().get(point));
            for (int point : DspSysModel.INSTANCE.getSeDiscrete().keySet())
                rtDataHandler.set(point + SUFFIEX_SE_DISCRETE, DspSysModel.INSTANCE.getSeDiscrete().get(point));
            int index = 0;
            rtDataHandler.set(KEY_SE_ISLAND_NUM, seResults.size());
            for (CimToIEEE bridge : seResults.keySet()) {
                index++;
                SeResultInfo result = seResults.get(bridge);
                rtDataHandler.set(PREFEX_SE_IS_CONVERGED + index, result.isConverged());
                if (!result.isConverged())
                    continue;
                rtDataHandler.set(PREFEX_SE_MODEL_INDEX + index, CalModelUtil.createIndex(bridge).toString());
                rtDataHandler.set(PREFEX_SE_IEEE_ISLAND + index, result.getPfResult().getIsland().toString());
            }
            if (yc != null)
                rtMeasureSaver.saveYC(yc);
            if (yx != null)
                rtMeasureSaver.saveYX(yx);
        } catch (Exception e) {
            e.printStackTrace();
            log.warn("Exception occur when updating SE result to rtdb:" + e);
        }
        log.info("State estimation result has been updated to memory cache.");
        log.info("Time used for updating SE result to rtdb:" + (System.currentTimeMillis() - start) + "ms");
    }

    public static void saveCimIndexToRtdb(String cimFile) {
        if (SystemModel.INSTANCE.getCimTimeStamp() == 0) {
            log.debug("There is no cim file loaded yet!");
            return;
        }
        if (isUpdatingCimIndex) {
            log.warn("Updating cim is running.");
            return;
        }
        isUpdatingCimIndex = true;
        lastCimFile = cimFile;
        CimResourceFilter filter = SystemModel.INSTANCE.getFilter();
        Map<String, IdentifiedObject> neededResource = filter.getNeededResource();
        List measureTypes = SystemModel.INSTANCE.getResourceManager().getResource(MeasurementType.class);
        //measure types are needed
        for (Object obj : measureTypes) {
            IdentifiedObject t = (IdentifiedObject) obj;
            neededResource.put(t.getMRID(), t);
        }
        CimToStringMap trans = new CimToStringMap(filter.getNeededResourceNum(), neededResource);
        trans.parse(cimFile);
        if (trans.getResult().size() < 1)
            return;
        log.info("Saving " + trans.getResult().size() + " needed objects to rtdb...");
        RtDataHandler rtDataHandler = DspSysModel.INSTANCE.getRtDataHandler();
        for (String key : trans.getResult().keySet())
            rtDataHandler.set(key, trans.getResult().get(key).toString());
        log.info("Save " + trans.getResult().size() + " needed objects to rtdb finished.");
        log.info("Saving cim resource index to rtdb... ");
        rtDataHandler.set(KEY_GENERATOR_IDS, SerializableUtil.formString(filter.getNeededGenerators().keySet()));
        rtDataHandler.set(KEY_LOAD_IDS, SerializableUtil.formString(filter.getNeededLoads().keySet()));
        rtDataHandler.set(KEY_BUSBAR_IDS, SerializableUtil.formString(filter.getNeededBusbars().keySet()));
        rtDataHandler.set(KEY_SHUNT_COMPENSATOR_IDS,
                SerializableUtil.formString(filter.getNeededShuntCompensators().keySet()));
        rtDataHandler.set(KEY_SERIES_COMPENSATOR_IDS,
                SerializableUtil.formString(filter.getNeededSeriesCompensators().keySet()));
        rtDataHandler.set(KEY_ACLINE_IDS, SerializableUtil.formString(filter.getNeededAclines().keySet()));
        rtDataHandler.set(KEY_TF_WINDING_IDS, SerializableUtil.formString(filter.getNeededWindings().keySet()));

        rtDataHandler.set(KEY_SUBSTATION_IDS, SerializableUtil.formString(filter.getNeededStations().keySet()));
        //rtDataHandler.set(KEY_SWITCH_IDS, SerializableUtil.formString(filter.getNeededSwitches().keySet()));
        rtDataHandler.set(KEY_TRANSFORMER_IDS, SerializableUtil.formString(filter.getNeededTransformers().keySet()));

        //rtDataHandler.set(KEY_ANALOG_IDS, SerializableUtil.formString(filter.getNeededAnalogs().keySet()));
        //rtDataHandler.set(KEY_DISCRETE_IDS, SerializableUtil.formString(filter.getNeededDiscretes().keySet()));

        for (String substationId : filter.getSubstationEquipMap().keySet()) {
            SubstationEquips substationEquips = filter.getSubstationEquipMap().get(substationId);
            List<String> equipmentIds = new ArrayList<String>(substationEquips.getEquipsNum());
            for (Equipment equip : substationEquips.getGenerators())
                equipmentIds.add(equip.getMRID());
            for (Equipment equip : substationEquips.getLoads())
                equipmentIds.add(equip.getMRID());
            for (Equipment equip : substationEquips.getBusbars())
                equipmentIds.add(equip.getMRID());
            for (Equipment equip : substationEquips.getShuntCompensators())
                equipmentIds.add(equip.getMRID());
            for (Equipment equip : substationEquips.getTransformers())
                equipmentIds.add(equip.getMRID());
            if (equipmentIds.size() > 0)
                rtDataHandler.set(substationId + SUFFIX_CONTAINED_EQUIPMENT, SerializableUtil.formString(equipmentIds));
        }

        for (String key : filter.getEquipMeasurements().keySet()) {
            ArrayList<String> ids = new ArrayList<String>(filter.getEquipMeasurements().get(key).size());
            for (MeasureInfo m : filter.getEquipMeasurements().get(key))
                ids.add(m.getId());
            if (ids.size() > 0)
                rtDataHandler.set(key + SUFFIX_CONTAINED_ANALOG, SerializableUtil.formString(ids));
        }
        updateStationInfo();//update informatin of substation like gen mw, mvar ability, capacitor, reactor mvar ability
        rtDataHandler.set(KEY_CIM_TIMESTAMP, SystemModel.INSTANCE.getCimTimeStamp());
        isUpdatingCimIndex = false;
        log.info("Save cim resource index to rtdb finished.");
    }


    private static void updateStationInfo() {
        RtDataHandler rtDataHandler = DspSysModel.INSTANCE.getRtDataHandler();
        boolean isStationGenMaxMwExist;
        double totalCapacitorMvar;
        double totalReactorMvar;
        double totalGenMw;
        double totalGenMvar;
        for (String stationId : SystemModel.INSTANCE.getFilter().getSubstationEquipMap().keySet()) {
            if(stationId.equals("260000006"))
                System.out.println();
            SubstationEquips equips = SystemModel.INSTANCE.getFilter().getSubstationEquipMap().get(stationId);
            totalCapacitorMvar = 0.0;
            totalReactorMvar = 0.0;
            totalGenMw = 0.0;
            totalGenMvar = 0.0;
            isStationGenMaxMwExist = true;
            for (ShuntCompensator c : equips.getShuntCompensators()) {
                if (c.getNominalMVAr() != null) {
                    double nominalMVar = Double.parseDouble(c.getNominalMVAr());
                    if (nominalMVar > 0)
                        totalCapacitorMvar += nominalMVar;
                    else
                        totalReactorMvar += nominalMVar;
                } else if (c.getMVArPerSection() != null) {
                    double mvarPerSection = Double.parseDouble(c.getMVArPerSection());
                    double maxSection = Double.parseDouble(c.getMaximumSections());
                    if (mvarPerSection > 0)
                        totalCapacitorMvar += maxSection * mvarPerSection;
                    else
                        totalReactorMvar += maxSection * mvarPerSection;
                }
            }
//            for (SynchronousMachine gen : equips.getGenerators()) {
//                GeneratingUnit genUnit = gen.getMemberOf_GeneratingUnit();
//                if (genUnit.getRatedGrossMaxMW() == null) {
//                    isStationGenMaxMwExist = false;
//                    continue;
//                }
//                double maxMW = Double.valueOf(genUnit.getRatedGrossMaxMW());
//                double sn = maxMW / 0.99;//todo: (simple: Pmax/0.99=Smax  Qmax=sqrt(Smax^2-P^2);
//                double maxMVAR = Math.sqrt(sn * sn - maxMW * maxMW);
//                totalGenMw += maxMW;
//                totalGenMvar += maxMVAR;
//            }

            for (SynchronousMachine gen : equips.getGenerators()) {
                GeneratingUnit genUnit = gen.getMemberOf_GeneratingUnit();
                if (genUnit.getMaximumOperatingMW() == null) {
                    System.out.println(gen.getName() + " have no MW data");
                    isStationGenMaxMwExist = false;
                    continue;
                }
                double maxMW = Double.valueOf(genUnit.getMaximumOperatingMW());
                double maxMVAR = Double.valueOf(gen.getMaximumMVAr());
                totalGenMw += maxMW;
                totalGenMvar += maxMVAR;
            }

            rtDataHandler.set(IdCreator.createStationCapMvarAbility(stationId), totalCapacitorMvar);
            rtDataHandler.set(IdCreator.createStationReactorMvarAbility(stationId), totalReactorMvar);
            if (isStationGenMaxMwExist) {
                rtDataHandler.set(IdCreator.createStationGenMwAbility(stationId), totalGenMw);
                rtDataHandler.set(IdCreator.createStationGenMvarAbility(stationId), totalGenMvar);
            }
        }
    }

    /**
     * this method save following to rtdb:
     * <br>every compensator's on or off state</br>
     * <br>every compensator's mvar </br>
     * <br>every substation's total compensator mvar</br>
     *
     * @param cimTopoIslands result of topology analysis
     */
    public static void updateCompensatorData(Collection<CimTopoIsland> cimTopoIslands) {
        RtDataHandler rtDataHandler = DspSysModel.INSTANCE.getRtDataHandler();
        Map<String, Double> assistMap1 = new HashMap<String, Double>();
        Map<String, Double> assistMap2 = new HashMap<String, Double>();
        for (CimTopoIsland island : cimTopoIslands) {
            Map<TopologicalNode, List<ShuntCompensator>> compensators = island.getShuntCompensators();
            for (List<ShuntCompensator> list : compensators.values()) {
                for (ShuntCompensator compensator : list) {
                    if (compensator.getNominalMVAr() != null) {
                        double nominalMvar = Double.parseDouble(compensator.getNominalMVAr());
                        if (nominalMvar > 0)
                            assistMap1.put(compensator.getMRID(), nominalMvar);
                        else
                            assistMap2.put(compensator.getMRID(), nominalMvar);
                    } else if (compensator.getMVArPerSection() != null) {
                        double mvarPerSection = Double.parseDouble(compensator.getMVArPerSection());
                        double maxSection = Double.parseDouble(compensator.getMaximumSections());
                        if (mvarPerSection > 0)
                            assistMap1.put(compensator.getMRID(), maxSection * mvarPerSection);
                        else
                            assistMap2.put(compensator.getMRID(), maxSection * mvarPerSection);
                    }
                }
            }
        }
        for (String stationId : SystemModel.INSTANCE.getFilter().getSubstationEquipMap().keySet()) {
            SubstationEquips equips = SystemModel.INSTANCE.getFilter().getSubstationEquipMap().get(stationId);
            double totalCapacitorMvar = 0.0;
            double totalReactorMvar = 0.0;
            for (ShuntCompensator c : equips.getShuntCompensators()) {
                if (assistMap1.containsKey(c.getMRID()) || assistMap2.containsKey(c.getMRID()))
                    rtDataHandler.set(IdCreator.createCompensatorState(c.getMRID()), true);
                else
                    rtDataHandler.set(IdCreator.createCompensatorState(c.getMRID()), false);
                if (assistMap1.containsKey(c.getMRID())) {
                    rtDataHandler.set(IdCreator.createCapacitorMvar(c.getMRID()), assistMap1.get(c.getMRID()));
                    totalCapacitorMvar += assistMap1.get(c.getMRID());
                } else if (assistMap2.containsKey(c.getMRID())) {
                    rtDataHandler.set(IdCreator.createReactorMvar(c.getMRID()), assistMap2.get(c.getMRID()));
                    totalReactorMvar += assistMap2.get(c.getMRID());
                }
            }
            rtDataHandler.set(IdCreator.createStationCapMvar(stationId), totalCapacitorMvar);
            rtDataHandler.set(IdCreator.createStationReactorMvar(stationId), totalReactorMvar);
        }
    }

    /**
     * this method save every power plant's total generator mw and mvar to rtdb.
     */
    private static void updatePlantGenPower() {
        if (SystemModel.INSTANCE.getCimTimeStamp() == 0) {
            log.debug("There is no cim file loaded yet!");
            return;
        }
        RtDataHandler rtDataHandler = DspSysModel.INSTANCE.getRtDataHandler();
        boolean isEveryGenHasMwMeas;
        boolean isEveryGenHasMvarMeas;
        double totalGenMw;
        double totalGenMvar;

        for (String stationId : SystemModel.INSTANCE.getFilter().getSubstationEquipMap().keySet()) {
            SubstationEquips equips = SystemModel.INSTANCE.getFilter().getSubstationEquipMap().get(stationId);
            if (equips.getGenerators() == null || equips.getGenerators().size() < 1)
                continue;
            isEveryGenHasMwMeas = true;
            isEveryGenHasMvarMeas = true;
            totalGenMw = 0.0;
            totalGenMvar = 0.0;

            for (SynchronousMachine c : equips.getGenerators()) {
                List<MeasureInfo> l = SystemModel.INSTANCE.getFilter().getEquipMeasurements().get(c.getMRID());
                if (l == null) {
                    isEveryGenHasMwMeas = false;
                    isEveryGenHasMvarMeas = false;
                    break;
                }
                MeasureInfo mwMeasure = null;
                MeasureInfo mvarMeasure = null;
                for (MeasureInfo m : l) {
                    if (m.getType().equals(TYPE_THREE_PHASE_ACTIVE_POWER)) {
                        if (mwMeasure != null)
                            log.warn("There are more than one mv measurements of the generator id: " + c.getMRID() + " name:" + c.getName());
                        mwMeasure = m;
                    } else if (m.getType().equals(TYPE_THREE_PHASE_REACTIVE_POWER)) {
                        if (mvarMeasure != null)
                            log.warn("There are more than one mvar measurements of the generator id: " + c.getMRID() + " name:" + c.getName());
                        mvarMeasure = m;
                    }
                }
                if (mwMeasure != null) {
                    Double d = DspSysModel.INSTANCE.getScadaAnalog().get(mwMeasure.getId());
                    if (d == null)
                        isEveryGenHasMwMeas = false;
                    else
                        totalGenMw += d;
                } else
                    isEveryGenHasMwMeas = false;
                if (mvarMeasure != null) {
                    Double d = DspSysModel.INSTANCE.getScadaAnalog().get(mvarMeasure.getId());
                    if (d == null)
                        isEveryGenHasMvarMeas = false;
                    else
                        totalGenMvar += d;
                } else
                    isEveryGenHasMvarMeas = false;
            }
            if (isEveryGenHasMwMeas)
                rtDataHandler.set(IdCreator.createStationGenMw(stationId), totalGenMw);
            if (isEveryGenHasMvarMeas)
                rtDataHandler.set(IdCreator.createStationGenMvar(stationId), totalGenMvar);
        }
    }

    /**
     * This method will update cim index to rtdb if there is no cim timestamp in rtdb
     */
    public static void check() {
        RtDataHandler rtDataHandler = DspSysModel.INSTANCE.getRtDataHandler();
        //check if cim index should be updated
        Object cim_time = rtDataHandler.get(KEY_CIM_TIMESTAMP);
        if (cim_time == null)
            saveCimIndexToRtdb(lastCimFile);
    }
}
